#!/usr/bin/env bash

# Copyright 2016 The Fuchsia Authors
#
# Use of this source code is governed by a MIT-style
# license that can be found in the LICENSE file or at
# https://opensource.org/licenses/MIT

# This script reads the dynamic symbol table of a DSO (ELF file) via nm
# and writes out a sequence of lines for each symbol in the DSO's ABI
# that can be used as C macros.  The macro invocations it writes look like:
#   TYPE(NAME, [ADDRESS,] SIZE)
# The ADDRESS parameter is included only if the script is given the -a switch.
# The SIZE is forced to 0 for functions if the script is given the -z switch.
# The TYPE is one of:
#   COMMON_OBJECT               STT_COMMON symbol
#   UNDEFINED                   SHN_UNDEF symbol
#   UNDEFINED_WEAK_OBJECT       SHN_UNDEF, STB_WEAK, STT_OBJECT symbol
#   UNDEFINED_WEAK              SHN_UNDEF, STB_WEAK, STT_FUNC symbol
#   WEAK_DATA_OBJECT            STB_WEAK, STT_OBJECT symbol
#   WEAK_FUNCTION               STB_WEAK, STT_FUNC symbol
#   FUNCTION                    STB_GLOBAL, STT_FUNC symbol
#   RODATA_OBJECT               STB_GLOBAL, STT_OBJECT symbol in R/O section
#   DATA_OBJECT                 STB_GLOBAL, STT_OBJECT symbol in R/W section
#   BSS_OBJECT                  STB_GLOBAL symbol in SHT_NOBITS section
# (nm actually uses a haphazard combination of ELF symbol details and
# section details to choose its type letters rather than directly using the
# ELF symbol bits consistently.  But the type letters map to these symbol
# details when symbols are defined in the usual ways by a compiler.)

if [ "$(basename $0)" = "sh" ]
then
  set -e
else
  set -o pipefail -e
fi

usage() {
  echo >&2 "Usage: $0 NM [-a] [-z] DSO OUTPUT [DEPFILE]"
  exit 2
}

if [ $# -lt 3 ]; then
  usage
fi

NM="$1"
shift

show_address=false
zero_function_size=false
while [ $# -gt 0 ]; do
  case "$1" in
  -a) show_address=true ;;
  -z) zero_function_size=true ;;
  *) break ;;
  esac
  shift
done

if [ $# -ne 2 -a $# -ne 3 ]; then
  usage
fi

DSO="$1"
OUTPUT="$2"
DEPFILE="$3"

USED_FILES=()
if [[ "$DSO" == @* ]]; then
  rspfile="${DSO#@}"
  DSO="$(< "$rspfile")"
  USED_FILES+=("$rspfile" "$DSO")
else
  USED_FILES+=("$DSO")
fi

if [ -n "$DEPFILE" ]; then
  echo "$OUTPUT: ${USED_FILES[*]}" > "$DEPFILE"
fi

dump_names() {
  "$NM" -P -g -D -S "$DSO"
}

massage() {
  local read name type addr size
  while read name type addr size; do

    # The numbers are printed in hex, but without a leading "0x" indicator.
    addr="0x$addr"
    if [ -z "$size" ]; then
      size="0x0"
    else
      size="0x$size"
    fi

    case "$name" in
    # Linkers sometimes emit these symbols into .dynsym, but they are useless
    # and should not be consider part of the ABI.
    __bss_start|__bss_start__|__bss_end__|_bss_end__) continue ;;
    __data_start|__end__|_stack|_etext|_edata|_end) continue ;;
    __start_xray_fn_idx|__stop_xray_fn_idx) continue ;;
    __start_xray_instr_map|__stop_xray_instr_map) continue ;;
    esac

    case "$type" in
    C) type=COMMON_OBJECT ;;
    U) type=UNDEFINED ;;
    v) type=UNDEFINED_WEAK_OBJECT ;;
    w) type=UNDEFINED_WEAK ;;
    V) type=WEAK_DATA_OBJECT ;;
    W) type=WEAK_FUNCTION ;;
    T) type=FUNCTION ;;
    R) type=RODATA_OBJECT ;;
    D) type=DATA_OBJECT ;;
    B) type=BSS_OBJECT ;;
    *)
      echo >&2 "$0: Unhandled type '${type}' for symbol '${name}'"
      exit 1
      ;;
    esac

    if $show_address; then
      address_item=", $addr"
    else
      address_item=
    fi

    if $zero_function_size; then
      case $type in
      FUNCTION|WEAK_FUNCTION) size="0x0" ;;
      esac
    fi

    echo "${type}(${name}${address_item}, ${size})"

  done
}

trap 'rm -f "$OUTPUT" ${DEPFILE:+$"DEPFILE"}' ERR HUP INT TERM
dump_names | massage > "$OUTPUT"
